<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8' />
    <title>显示具有自定义属性的HTML聚类</title>
    <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
    <link href="https://cdn.jsdelivr.net/gh/niceFutrue/happyacomma/mapPack/global.min.css" rel="stylesheet">
    <script src='https://cdn.jsdelivr.net/gh/niceFutrue/happyacomma/mapPack/global.js'></script>
    <style>
        body,*{ margin:0; padding:0; outline: 0;}
        .polymer{max-width: 1200px;height:100vh;min-height:500px;margin:auto;position: relative;}
        #map { position:absolute; top:0; bottom:0; width:100%; }
        .marker{width: 18px;height: 18px;border-radius: 50%;background: linear-gradient(45deg,hsl(10,100%,50%),hsl(30,100%,50%));background-size: cover;border: 2px solid #fff;transition: width .3s ease-out,height .3s ease-out;cursor: pointer;box-shadow: 0 16px 48px 0 rgba(0,0,0,.08), 0 6px 12px 0 rgba(0,0,0,.1);}
        .marker:before{width:100%;height:100%;content: "";position: absolute;background-image: var(--picture);background-size: cover;border-radius: 50%;transition: opacity .5s ease-out;opacity: 0;}
        .marker:hover{width: 60px;height: 60px;z-index: 33;}
        .marker:hover:before{opacity: 1;}
        .mapboxgl-control-container .mapboxgl-ctrl-bottom-left,.mapboxgl-ctrl .mapboxgl-ctrl-compass{display: none;}
    </style>
</head>
<body>
<div class="polymer">
    <div id="map"></div>
</div>

<script>
    // mapboxgl.accessToken = '换上你的token';
    mapboxgl.accessToken = '';
    var map = new mapboxgl.Map({
        container: 'map',
        style: 'mapbox://styles/happyacomma/cku68t32435ao19rsobl1d17j', // 换上你的自定义地图
        center: [114.05229630906956, 22.55193218784399],
        pitch: 21, // 倾斜度
        maxZoom: 18,
        minZoom: 3,
        zoom: 3,
        attributionControl: false  // 右下角标识
    });

    map.addControl(new mapboxgl.NavigationControl());

    map.on('load', function () {
        // 数据源
        map.addSource('footprints', {
            "type": "geojson",
            "data": "http://happycommon.top/public/static/mapbox/map.json?v5",// 换上你的自定义地图
            "cluster": true,
            "clusterRadius": 80
        });
        // 聚合点 分级
        map.addLayer({
            "id": "footprint_circle",
            "type": "circle",
            "source": "footprints",
            "filter": ["!=", "point_count", true],
            "paint": {
                'circle-color': [
                    'step',
                    ['get', 'point_count'],
                    '#6ECC39', 5, '#ffc107'
                ],
                'circle-radius': [
                    'step',
                    ['get', 'point_count'],
                    16, 5, 16
                ],
                'circle-stroke-width': 2,
                'circle-stroke-color': '#fff'
            }
        });
        // 聚合点 中间默认的文字设置
        map.addLayer({
            id: 'footprint-count',
            type: 'symbol',
            source: 'footprints',
            filter: ['has', 'point_count'],//有point_count属性的，为聚合点
            layout: {
                'text-field': '{point_count}',
                'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'],
                'text-size': 16,
                'icon-ignore-placement': true,
            },
            paint: { 'text-color': '#fff', 'text-opacity': 1 },
        });
        // 不聚合的圆
        map.addLayer({
            id: 'unclustered-point',
            type: 'circle',
            source: "footprints",
            filter: ['!', ['has', "point_count"]],
            paint: {
                'circle-color': '#ff6600',
                'circle-radius': 10,
                'circle-stroke-width': 2,
                'circle-stroke-color': '#fff'
            }
        });
        // 对象，用于缓存和跟踪HTML标记对象（为了性能）
        var markers = {};         //标记
        var markersOnScreen = {}; //画面上的标记
        function updateMarkers() {
            var newMarkers = {};
            var features = map.querySourceFeatures('footprints');
            for (var i = 0; i < features.length; i++) {
                let coords = features[i].geometry.coordinates;
                let props = features[i].properties;
                if (props.point_count) continue;
                var pId = props.id;
                let marker = markers[pId];
                if (!marker) {
                    let el = createDonutChart(props);
                    marker = markers[pId] = new mapboxgl.Marker({element: el}).setLngLat(coords).addTo(map);
                }
                newMarkers[pId] = marker;
            }
            // 对于我们之前添加的每个标记，删除那些不再可见的标记。
            for (pId in markersOnScreen) {
                if (!newMarkers[pId]){
                    markersOnScreen[pId].remove();
                }
            }
            markersOnScreen = newMarkers;
        }
        // 聚合点
        map.on('click', 'footprint_circle', (e) => {
            const features = map.queryRenderedFeatures(e.point,{layers: ['footprint_circle']});
            const clusterId = features[0].properties.cluster_id;
            if(clusterId){
                map.getSource("footprints").getClusterExpansionZoom(clusterId,
                    (err, zoom) => {
                        if (err){return}
                        map.easeTo({
                            center: e.lngLat,
                            zoom: zoom
                        });
                    }
                );
            }
        });
        // 不聚合点
        map.on('click', 'unclustered-point', (e) => {
            const coordinates = e.features[0].geometry.coordinates.slice();
            const name = e.features[0].properties.name;
            const text = e.features[0].properties.text;
            // 确保如果地图被放大，以至于多个副本是可见的，那么窗口出现在被指向的副本上。
            while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
                coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
            }
            new mapboxgl.Popup({ offset: 20 }).setLngLat(e.lngLat)
                .setHTML(`<b>${name}</b><div> ${text}</div>`).addTo(map);
        });
        // 鼠标不聚合点
        map.on('mouseenter', 'unclustered-point', (e) => {
            updateMarkers()
        });

        map.on('data', function (e) {
            if (e.sourceId !== 'footprints' || !e.isSourceLoaded) return;
            updateMarkers();
            map.on('move', updateMarkers); //在用户交互或Map#jumpTo等方法引发视图动态转换期间重复触发
            map.on('moveend', updateMarkers); //在用户交互或 Map#jumpTo等方法的作用下，地图完成从一个视图到另一个视图的转换后启动该事件
        });
    });
    // point点 图标
    function createDonutChart(props) {
        let html = '<div class="marker" style="--picture:url(' + props.img + ')"></div>';
        let el = document.createElement('div');
        el.innerHTML = html;
        return el.firstChild;
    }
</script>

</body>
</html>